package cn.swust.designpatternlearningplatform.service.impl;

import cn.swust.designpatternlearningplatform.dao.*;
import cn.swust.designpatternlearningplatform.domain.bo.PaperBo;
import cn.swust.designpatternlearningplatform.domain.bo.PaperParamBo;
import cn.swust.designpatternlearningplatform.domain.dto.PaperDto;
import cn.swust.designpatternlearningplatform.domain.dto.TopicDto;
import cn.swust.designpatternlearningplatform.domain.dto.TopicOptionDto;
import cn.swust.designpatternlearningplatform.domain.entity.KnowledgePoint;
import cn.swust.designpatternlearningplatform.domain.entity.Topic;
import cn.swust.designpatternlearningplatform.domain.entity.TopicIndicators;
import cn.swust.designpatternlearningplatform.domain.enums.TopicTypeEnum;
import cn.swust.designpatternlearningplatform.service.IntelligentTestPaperGenerationService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;

@Slf4j
@Service
public class GAIntelligentTestPaperGenerationServiceImpl implements IntelligentTestPaperGenerationService {

    /**
     * 题目基本访问层
     *
     * @author Tang
     * @date 2023/5/16 16:51
     **/
    @Resource
    private TopicDao topicDao;
    /**
     * 题目指标访问层
     *
     * @author Tang
     * @date 2023/5/16 16:51
     **/
    @Resource
    private TopicIndicatorsDao topicIndicatorsDao;
    /**
     * 题目与知识点关系访问层
     *
     * @author Tang
     * @date 2023/5/17 11:02
     **/
    @Resource
    private TopicKnowledgeCorrelationDao topicKnowledgeCorrelationDao;
    /**
     * 知识点信息访问层
     *
     * @author Tang
     * @date 2023/5/17 11:01
     **/
    @Resource
    private TopicKnowledgePointDao topicKnowledgePointDao;
    /**
     * 题目选项访问层
     *
     * @author Tang
     * @date 2023/5/22 20:37
     **/
    @Resource
    private TopicOptionDao topicOptionDao;
    /**
     * 种群数量
     *
     * @author Tang
     * @date 2023/5/16 17:22
     **/
    @Value("${GA.paper.initSize:50}")
    private Integer populationSize;
    /**
     * 难度系数，区分度，知识点覆盖率分别的权值
     *
     * @author Tang
     * @date 2023/5/16 23:38
     **/
    @Value("${GA.weight:0.4,0.3,0.3}")
    private String[] weight;
    /**
     * 计算过程中数字保留的位数
     *
     * @author Tang
     * @date 2023/5/17 19:55
     **/
    @Value("${GA.scale:4}")
    private Integer scale;
    /**
     * 交叉率和变异率的系数
     *
     * @author Tang
     * @date 2023/5/22 20:04
     **/
    @Value("${GA.ks:0.9,0.6}")
    private String[] ks;
    /**
     * 结果平均值波动范围，在一定范围内认为最优
     *
     * @author Tang
     * @date 2023/5/22 20:05
     **/
    @Value("${GA.range:2.0}")
    private String range;
    /**
     * 波动连续代数
     * @author Tang
     * @date 2023/5/23 0:54
    **/
    @Value("${GA.continuous:10}")
    private Integer continuous;
    /**
     * 进行智能组卷
     *
     * @param params 组卷参数（对试卷的期望难度，期望知识点覆盖率，期望区分度（开发人员自己调控）），
     *               在params里面加入(expectedDifficulty,value)，(expectedKnowledgePointCoverage,value)，
     *               (expectedDifferentiation,value)，其中value为期望值
     * @return cn.swust.designpatternlearningplatform.domain.dto.PaperDto 组好的试卷
     * @author Tang
     * @date 2023/5/15 17:02
     **/
    @Override
    public PaperDto composition(PaperParamBo params) {
        //初始化数据
        //创建试卷
        PaperDto paperDto = new PaperDto();
        paperDto.setTotalScore(params.getTotalScore());
        paperDto.setEachJudgeScore(params.getEachJudgeScore());
        paperDto.setEachChoiceScore(params.getEachChoiceScore());
        paperDto.setEachMultipleChoiceScore(params.getEachMultipleChoiceScore());
        paperDto.setEachShortAnswerQuestionScore(params.getEachShortAnswerQuestionScore());
        //获取本次组卷所需的题库
        PaperBo.QuestionTypes questionTypes = preprocessingData(params);
        //获取题库中各题目的题目指标
        Map<Integer, TopicIndicators> topicIndicators = getTopicIndicators(questionTypes.getAllTopicIds());
        //随机生成试卷种群
        List<PaperBo> population = PaperBo.getInitialPopulation(populationSize, questionTypes, ThreadLocalRandom.current());
        //计算适应度
        population.forEach(i -> {
            BigDecimal fitness = calculateFitness(i, params, topicIndicators);
            i.setFitness(fitness);
        });
        //最大适应度和平均适应度
        BigDecimal fm = BigDecimal.ZERO, fn;
        //数据
        List<BigDecimal> data = new ArrayList<>();
        //差分数组
        List<BigDecimal> differentialArray = new ArrayList<>();
        //最佳个体
        PaperBo best = null;
        for (int i = 0; i < populationSize; i++) {
            System.out.println("第" + i + "次");
            //自然选择
            List<PaperBo> naturalSelection = naturalSelection(population);
            System.out.println("自然选择完毕");
            //交叉
            List<PaperBo> hybridize = hybridize(questionTypes, naturalSelection,
                    maxFitness(naturalSelection), averageFitness(naturalSelection),
                    params, topicIndicators);
            System.out.println("交叉完毕");
            //变异
            List<PaperBo> variation = variation(questionTypes, hybridize,
                    maxFitness(hybridize), averageFitness(hybridize),
                    params, topicIndicators);
            System.out.println("变异完毕");
            population = variation;
            // 计算种群最大适应度
            fm = maxFitness(variation);
            //计算种群平均适应度
            fn = averageFitness(variation);
            System.out.println("fm:" + fm);
            System.out.println("fn:" + fn);
            if (resultFluctuationJudgment(data, differentialArray, fn)) {
                break;
            }

            for (PaperBo paperBo : population) {
                if (fm.equals(paperBo.getFitness())) {
                    if (best == null) {
                        best = paperBo;
                        break;
                    }
                    if (paperBo.getFitness().compareTo(best.getFitness()) >= 0) {
                        best = paperBo;
                    }
                }
            }
        }
        return best == null ? PaperBo2PaperDto(paperDto, population.get(0)) : PaperBo2PaperDto(paperDto, best);
    }

    /**
     * 初始化题库数据和试卷参数
     *
     * @param params
     * @return cn.swust.designpatternlearningplatform.domain.bo.PaperBo.QuestionTypes
     * @author Tang
     * @date 2023/5/16 17:37
     **/
    private PaperBo.QuestionTypes preprocessingData(PaperParamBo params) {
        PaperBo.QuestionTypes questionTypes = new PaperBo.QuestionTypes();
        //初始化所需题库数据
        questionTypes.setJudge(getTopicIdsByType(TopicTypeEnum.JUDGE, params.getJudgeNum()), params.getJudgeNum());
        questionTypes.setChoice(getTopicIdsByType(TopicTypeEnum.SINGLE, params.getChoiceNum()), params.getChoiceNum());
        questionTypes.setMultipleChoice(getTopicIdsByType(TopicTypeEnum.MULTIPLE, params.getMultipleChoiceNum()), params.getMultipleChoiceNum());
        questionTypes.setShortAnswerQuestion(getTopicIdsByType(TopicTypeEnum.SHORT_ANSWER_QUESTION, params.getShortAnswerQuestionNum()), params.getShortAnswerQuestionNum());
        return questionTypes;
    }

    /**
     * 获取指定题型的题目id
     *
     * @param type
     * @param num
     * @return java.util.List<java.lang.Integer>
     * @author Tang
     * @date 2023/5/17 0:02
     **/
    private List<Integer> getTopicIdsByType(TopicTypeEnum type, int num) {
        if (num > 0) {
            return topicDao.selectTopicIdByTopicType(type.typeName);
        }
        return new ArrayList<>();
    }

    /**
     * 获取题库中所有题的指标，方便后续计算
     *
     * @param topicIds
     * @return java.util.Map<java.lang.Integer, cn.swust.designpatternlearningplatform.domain.entity.TopicIndicators>
     * @author Tang
     * @date 2023/5/17 0:14
     **/
    private Map<Integer, TopicIndicators> getTopicIndicators(List<Integer> topicIds) {
        //记录所有的题目指标
        Map<Integer, TopicIndicators> topicIndicators = new HashMap<>();
        topicIds.forEach(i -> {
            //根据题目id查询到题目指标
            TopicIndicators byTopicId = topicIndicatorsDao.selectByTopicId(i);
            //添加到hash表中
            topicIndicators.put(i, byTopicId);
        });
        return topicIndicators;
    }

    /**
     * 适应度组成计算
     *
     * @param weight
     * @param expect
     * @param actual
     * @return java.math.BigDecimal
     * @author Tang
     * @date 2023/5/17 19:59
     **/
    private BigDecimal calculateFitnessPartly(BigDecimal weight, BigDecimal expect, BigDecimal actual) {
        try {
            if (expect.compareTo(actual) == 0) {
                return BigDecimal.valueOf(Double.MAX_VALUE);
            }
            BigDecimal var1 = BigDecimal.ONE;
            BigDecimal var2 = expect.subtract(actual).abs().multiply(weight);
            return var1.divide(var2, scale, RoundingMode.HALF_UP);
        } catch (ArithmeticException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 计算试卷的适应度
     *
     * @param aim
     * @param params
     * @param topicIndicators
     * @return java.math.BigDecimal
     * @author Tang
     * @date 2023/5/17 20:11
     **/
    private BigDecimal calculateFitness(PaperBo aim, PaperParamBo params, Map<Integer, TopicIndicators> topicIndicators) {
        //试卷实际参数
        //知识点
        //知识点总数
        Integer pointNum = topicKnowledgePointDao.selectKnowledgePointNum();
        //该试卷所涉及的知识点
        Set<Integer> pointIds = new HashSet<>(pointNum);
        //试卷总分
        BigDecimal totalScore = params.getTotalScore();
        //难度系数
        BigDecimal actualDifficulty = BigDecimal.ZERO;
        //区分度
        BigDecimal actualDifferentiation = BigDecimal.ZERO;
        //知识点覆盖率
        BigDecimal actualKnowledgePointCoverage = BigDecimal.ZERO;

        //计算试卷的难度和区分度
        for (int i = 0; i < aim.getAllTopic().size(); i++) {
            for (int j = 0; j < aim.getAllTopic().get(i).size(); j++) {
                Integer topicId = aim.getAllTopic().get(i).get(j);
                //获取id为j的题目的指标
                TopicIndicators indicators = topicIndicators.get(topicId);
                //计算实际难度系数
                actualDifficulty = actualDifficulty.add(indicators.getDegreeOfDifficulty().multiply(params.getEachScore()[i])
                        .divide(totalScore, scale, RoundingMode.HALF_UP));
                //计算实际区分度
                actualDifferentiation = actualDifferentiation.add(indicators.getDiscrimination().multiply(params.getEachScore()[i]))
                        .divide(totalScore, scale, RoundingMode.HALF_UP);
            }
        }

        //计算知识点覆盖率
        aim.getAllTopic().forEach(j -> j.forEach(i -> {
            List<Integer> linkedWithPoint = topicKnowledgeCorrelationDao.selectTopicLinkedWithPoint(i);
            List<List<Integer>> sonPoints = linkedWithPoint
                    .stream().map(k -> topicKnowledgePointDao.selectKnowledgePointSonId(k)
                            .stream().map(KnowledgePoint::getPointId).collect(Collectors.toList())).collect(Collectors.toList());
            pointIds.addAll(linkedWithPoint);
            sonPoints.forEach(pointIds::addAll);
        }));
        actualKnowledgePointCoverage = actualKnowledgePointCoverage.add(pointNum == 0 ?
                BigDecimal.ZERO : BigDecimal.valueOf(pointIds.size()).divide(BigDecimal.valueOf(pointNum), scale, RoundingMode.HALF_UP));

        //试卷期望参数
        BigDecimal expectedDifficulty = params.getExpectedDifficulty();
        BigDecimal expectedDifferentiation = params.getExpectedDifferentiation();
        BigDecimal expectedKnowledgePointCoverage = params.getExpectedKnowledgePointCoverage();

        //适应度计算
        return calculateFitnessPartly(new BigDecimal(weight[0]), expectedDifficulty, actualDifficulty)
                .add(calculateFitnessPartly(new BigDecimal(weight[1]), expectedDifferentiation, actualDifferentiation))
                .add(calculateFitnessPartly(new BigDecimal(weight[2]), expectedKnowledgePointCoverage, actualKnowledgePointCoverage));
    }

    /**
     * 自适应计算
     *
     * @param k  自适应系数，根据实际情况灵活取值
     * @param fm 最大适应度
     * @param fn 平均适应度
     * @param f  个体适应度
     * @return java.math.BigDecimal
     * @author Tang
     * @date 2023/5/20 9:23
     **/
    private BigDecimal adaptiveComputing(BigDecimal k, BigDecimal fm, BigDecimal fn, BigDecimal f) {
        if (f.compareTo(fn) < 0 || fm.equals(fn)) {
            return k;
        }
        return fm.subtract(f)
                .divide(fm.subtract(fn), scale, RoundingMode.HALF_UP)
                .multiply(k);
    }

    /**
     * 返回种群中最大的适应度
     *
     * @param population
     * @return java.math.BigDecimal
     * @author Tang
     * @date 2023/5/22 18:32
     **/
    private BigDecimal maxFitness(List<PaperBo> population) {
        return population.stream().map(PaperBo::getFitness)
                .max(BigDecimal::compareTo)
                .orElse(population.get(0).getFitness());
    }

    /**
     * 返回种群中平均适应度
     *
     * @param population
     * @return java.math.BigDecimal
     * @author Tang
     * @date 2023/5/22 18:32
     **/
    private BigDecimal averageFitness(List<PaperBo> population) {
        return population.stream().map(PaperBo::getFitness).reduce(BigDecimal.ZERO, BigDecimal::add)
                .divide(BigDecimal.valueOf(population.size()), scale, RoundingMode.HALF_UP);
    }

    /**
     * 轮盘赌算法选择个体
     *
     * @param roulette
     * @param population
     * @return cn.swust.designpatternlearningplatform.domain.bo.PaperBo
     * @author Tang
     * @date 2023/5/19 22:03
     **/
    private PaperBo rouletteAlgorithm(List<BigDecimal> roulette, List<PaperBo> population) {
        //生成一个[0,1]的随机浮点数
        BigDecimal randomValue = BigDecimal.valueOf(ThreadLocalRandom.current().nextDouble());
        int i = 1;
        for (; i < roulette.size(); i++) {
            //如果生成的小于等于第i个，则留下第i个
            if (randomValue.compareTo(roulette.get(i)) <= 0) {
                break;
            }
        }
        if (i == roulette.size()) {
            return population.get(population.size() - 1);
        }
        return population.get(i - 1);
    }

    /**
     * 自然选择
     *
     * @param population
     * @return java.util.List<cn.swust.designpatternlearningplatform.domain.bo.PaperBo>
     * @author Tang
     * @date 2023/5/19 21:53
     **/
    private List<PaperBo> naturalSelection(List<PaperBo> population) {
        //构建轮盘
        List<BigDecimal> roulette = new ArrayList<>(population.size() + 1);
        roulette.add(BigDecimal.ZERO);
        //种群适应度
        BigDecimal all = population.stream().map(PaperBo::getFitness).reduce(BigDecimal.ZERO, BigDecimal::add);
        population.forEach(i -> {
            roulette.add(i.getFitness().divide(all, scale, RoundingMode.HALF_UP).add(roulette.get(roulette.size() - 1)));
        });
        //经过自然选择后的种群
        List<PaperBo> populationAfterSelection = new ArrayList<>(populationSize);
        while (populationAfterSelection.size() < populationSize) {
            populationAfterSelection.add(rouletteAlgorithm(roulette, population));
        }
        return populationAfterSelection;
    }

    /**
     * 随机从种群内抽一个个体
     *
     * @param population
     * @return cn.swust.designpatternlearningplatform.domain.bo.PaperBo
     * @author Tang
     * @date 2023/5/20 9:00
     **/
    private PaperBo randomChoice(List<PaperBo> population) {
        return population.stream()
                .skip(ThreadLocalRandom.current().nextInt(population.size()))
                .findFirst()
                .orElse(null);
    }

    /**
     * 两两配对选择
     *
     * @param population
     * @return java.util.List<cn.swust.designpatternlearningplatform.domain.bo.PaperBo>
     * @author Tang
     * @date 2023/5/20 8:59
     **/
    private PaperBo[] randomChoiceTwo(List<PaperBo> population) {
        //抽取第一个个体
        PaperBo first = randomChoice(population);
        //抽取第二个个体
        PaperBo second = randomChoice(population);
        while (second == null) {
            second = randomChoice(population);
        }
        PaperBo[] paperBos = {first, second};
        Arrays.sort(paperBos, (a, b) -> b.getFitness().compareTo(a.getFitness()));
        return paperBos;
    }

    /**
     * 随机在总题库内获取不同的题目
     *
     * @param all      总体库
     * @param typeList 试卷某题型的题库
     * @return java.lang.Integer
     * @author Tang
     * @date 2023/5/22 18:46
     **/
    private Integer randomGetUniqueInType(List<Integer> all, List<Integer> typeList) {
        Integer aim = null;
        while (typeList.contains(aim) || aim == null) {
            aim = all.get(ThreadLocalRandom.current().nextInt(all.size()));
        }
        return aim;
    }

    /**
     * 交换基因片段
     *
     * @param all   该题型的总题库
     * @param a
     * @param b
     * @param index
     * @return void
     * @author Tang
     * @date 2023/5/22 12:13
     **/
    private void exchangeGeneSegment(List<Integer> all, List<Integer> a, List<Integer> b, Integer index) {
        //改变bSegment片段
        Integer bSegment = randomGetUniqueInType(all, a);
        //改变aSegment片段
        Integer aSegment = randomGetUniqueInType(all, b);
        //交换
        a.set(index, bSegment);
        b.set(index, aSegment);
    }

    /**
     * 交叉计算
     *
     * @param questionTypes   试卷各题型参数
     * @param population      试卷种群
     * @param fm              最大适应度
     * @param fn              平均适应度
     * @param params          试卷参数（用于智能组卷）
     * @param topicIndicators 题库中各题目的题目指标
     * @return java.util.List<cn.swust.designpatternlearningplatform.domain.bo.PaperBo>
     * @author Tang
     * @date 2023/5/22 12:33
     **/
    private List<PaperBo> hybridize(PaperBo.QuestionTypes questionTypes, List<PaperBo> population,
                                    BigDecimal fm, BigDecimal fn,
                                    PaperParamBo params, Map<Integer, TopicIndicators> topicIndicators) {
        //获取总题库（含各种题型）
        List<List<Integer>> allTopic = questionTypes.getAllTopic();
        for (int k = 0; k < populationSize / 2; k++) {
            //随机两两配对
            PaperBo[] paperBos = randomChoiceTwo(population);
            //选取适应度最大的作为计算交叉率的量，以保证优良基因得以保存
            BigDecimal pc = adaptiveComputing(new BigDecimal(ks[0]), fm, fn, paperBos[0].getFitness());
            List<List<Integer>> paperBo1 = paperBos[0].getAllTopic();
            List<List<Integer>> paperBo2 = paperBos[1].getAllTopic();
            for (int i = 0; i < paperBo1.size(); i++) {
                List<Integer> all = allTopic.get(i);
                List<Integer> list1 = paperBo1.get(i);
                List<Integer> list2 = paperBo2.get(i);
                for (int j = 0; j < list1.size(); j++) {
                    if (BigDecimal.valueOf(ThreadLocalRandom.current().nextDouble()).compareTo(pc) >= 0) {
                        exchangeGeneSegment(all, list1, list2, j);
                    }
                }
            }
            //重新计算适应度
            for (PaperBo paperBo : paperBos) {
                paperBo.setFitness(calculateFitness(paperBo, params, topicIndicators));
            }
        }
        return population;
    }

    /**
     * 变异计算
     *
     * @param questionTypes   试卷各题型参数
     * @param population      试卷种群
     * @param fm              最大适应度
     * @param fn              平均适应度
     * @param params          试卷参数（用于智能组卷）
     * @param topicIndicators 题库中各题目的题目指标
     * @return java.util.List<cn.swust.designpatternlearningplatform.domain.bo.PaperBo>
     * @author Tang
     * @date 2023/5/22 19:36
     **/
    private List<PaperBo> variation(PaperBo.QuestionTypes questionTypes, List<PaperBo> population,
                                    BigDecimal fm, BigDecimal fn,
                                    PaperParamBo params, Map<Integer, TopicIndicators> topicIndicators) {
        //获取总题库（含各种题型）
        List<List<Integer>> typesAllTopic = questionTypes.getAllTopic();
        for (PaperBo paperBo : population) {
            //计算变异率
            BigDecimal pm = adaptiveComputing(new BigDecimal(ks[1]), fm, fn, paperBo.getFitness());
            List<List<Integer>> paperBoAllTopic = paperBo.getAllTopic();
            for (int j = 0; j < paperBoAllTopic.size(); j++) {
                List<Integer> all = typesAllTopic.get(j);
                List<Integer> typeList = paperBoAllTopic.get(j);
                for (int i = 0; i < typeList.size(); i++) {
                    if (BigDecimal.valueOf(ThreadLocalRandom.current().nextDouble()).compareTo(pm) >= 0) {
                        typeList.set(i, -1);
                        typeList.set(i, randomGetUniqueInType(all, typeList));
                    }
                }
            }
            //重新计算适应度
            paperBo.setFitness(calculateFitness(paperBo, params, topicIndicators));
        }
        return population;
    }

    /**
     * 结果波动判断
     *
     * @param differentialArray 差分数组
     * @param current           每轮结果
     * @return java.lang.Boolean
     * @author Tang
     * @date 2023/5/22 19:52
     **/
    private Boolean resultFluctuationJudgment(List<BigDecimal> data, List<BigDecimal> differentialArray, BigDecimal current) {
        data.add(current);
        Integer size = differentialArray.size();
        if (size.equals(0)) {
            differentialArray.add(current);
            return false;
        }
        BigDecimal pre = data.get(size - 1);
        BigDecimal subtract = pre.subtract(current);
        BigDecimal s = new BigDecimal(range);
        BigDecimal up = s.multiply(BigDecimal.valueOf(2L));
        differentialArray.add(subtract);
        int k = 0;
        for (int i = size - 1; i >= 0 && k < continuous; i--) {
            BigDecimal now = differentialArray.get(i).add(s);
            if (now.compareTo(BigDecimal.ZERO) >= 0 && now.compareTo(up) <= 0) {
                k++;
            } else {
                k = 0;
            }
        }
        return k >= continuous;
    }

    /**
     * 根据id获取topicDto
     *
     * @param topicId
     * @return java.util.List<cn.swust.designpatternlearningplatform.domain.dto.TopicDto>
     * @author Tang
     * @date 2023/5/22 20:41
     **/
    private List<TopicDto> getTopicDto(List<Integer> topicId) {
        return topicId.stream().map(i -> {
            Topic topic = topicDao.selectTopicById(i);
            TopicDto topicDto = new TopicDto();
//            BeanUtils.copyProperties(topic, topicDto, "topicAnswer", "topicAnalyse", "topicAnalysePath", "topicDifficulty");
            BeanUtils.copyProperties(topic, topicDto, "topicAnswer", "topicAnalyse", "topicAnalysePath");
            //查询题目选项
            List<TopicOptionDto> topicOptionDtos = topicOptionDao.getOptionsOfTopic(i).stream().map(j -> {
                TopicOptionDto topicOptionDto = new TopicOptionDto();
                BeanUtils.copyProperties(j, topicOptionDto);
                return topicOptionDto;
            }).collect(Collectors.toList());
            topicDto.setTopicOptionDtos(topicOptionDtos);
            return topicDto;
        }).collect(Collectors.toList());
    }

    /**
     * PaperBo转PaperDto
     *
     * @param source
     * @param paperBo
     * @return cn.swust.designpatternlearningplatform.domain.dto.PaperDto
     * @author Tang
     * @date 2023/5/22 20:41
     **/
    private PaperDto PaperBo2PaperDto(PaperDto source, PaperBo paperBo) {
        source.setJudgeList(getTopicDto(paperBo.getJudgeList()));
        source.setChoiceList(getTopicDto(paperBo.getChoiceList()));
        source.setMultipleChoiceList(getTopicDto(paperBo.getMultipleChoiceList()));
        source.setShortAnswerQuestionList(getTopicDto(paperBo.getShortAnswerQuestionList()));
        return source;
    }
}
